package moon.core.classdef;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import moon.core.Name;
import moon.core.NamedObject;
import moon.core.genericity.FormalPar;
import moon.core.instruction.CompoundInstr;
import moon.core.instruction.Instr;
import moon.traverse.Visitor;

/** 
 * Method of a class.
 * 
 * @author <a href="mailto:rmartico@ubu.es">Ra&uacute;l Marticorena</a>
 * @since JavaMoon-1.3
 */
@SuppressWarnings("serial")
public abstract class MethDec extends NamedObject implements Property {
	
	/**
	 * Separator character used in name convention.
	 */
	private static final String SEPARATOR = "~";
	
	
	/**
	 * Class where the method is declared.
	 * 
     * @since JavaMoon-1.3.5
	 */
	private ClassDef classDef;
    
    /**
     * Method implemented.
     */    
    private boolean deferred;
        
	/**
	 * Formal parameters if the method is generic.
	 */
	private List<FormalPar> formalParameters;
    
    /** 
     * Ordered set of formal arguments.
     *
     * @see moon.core.classdef.FormalArgument
     */    
    private List<FormalArgument> arguments;
    
    /**
     * List of local variables.
     */
    private List<LocalDec> localDecs;
    
    /**
     * List of instruction in a method body.
     */
    private List<Instr> instructions;
    
    /**
     * Gets the instructions.
     * 
     * @return instructions of body method
     * @see #setInstructions
     */
    public List<Instr> getInstructions(){
    	return instructions;
    }
    
    /**
     * Gets the instructions without block information as a flat list.
     * 
     * @return flat list of instructions
     */
    public List<Instr> getFlattenedInstructions(){
    	List<Instr> newBodyMethod = new ArrayList<Instr>();
		// removing instructions to new method
		
		for (Instr instr : this.getInstructions()){
			if (! (instr instanceof CompoundInstr )){
				
				newBodyMethod.add(instr);
			}
			else {
				visit((CompoundInstr)instr, newBodyMethod);
			}
		}	
		return newBodyMethod;
    }
    
    /**
     * Sets the instructions.
     * 
     * @param list list of instructions
     * @see #getInstructions
     */
    public void setInstructions(List<Instr> list){
    	instructions = list;
    }
    
    /**
     * Gets the formal parameters.
     * 
     * @return formal parameters
     */
    public List<FormalPar> getFormalPar(){
    	return formalParameters;
    }
    
    /**
     * Gets the signature as list of types.
     * 
     * @return formal parameters
     */
    public List<Type> getSignature(){
    	List<FormalArgument> list = this.getFormalArgument();
    	List<Type> types = new ArrayList<Type>();
    	for (FormalArgument fa : list){
    		types.add(fa.getType());
    	}
    	return types;
    }
    
    /**
     * Gets the formal arguments.
     * 
     * @return formal arguments
     */
    public List<FormalArgument> getFormalArgument(){
    	return arguments;
    }
    
    /**
     * Gets the position of a formal argument.
     * 
     * @param uniqueName argument name
     * @return position of the formal argument or -1 in other case
     */
    public int getIndexFormalArg(String uniqueName){
    	 	List<FormalArgument> list = this.getFormalArgument();
    	 	int position = 0;
    	 	for (FormalArgument fa : list){
    	 		if (fa.getUniqueName().toString().equals(uniqueName)){
    	 			return position;
    	 		}
    	 		position++;
    	 	}
    	 	return -1;
    }
    
    /**
     * Sets the list of formal arguments.
     * 
     * @param arguments arguments 
     */
    public void setFormalArguments(List<FormalArgument> arguments){
    	this.arguments = arguments;
    }     
    
    /**
     * Adds a local variable.
     * 
     * @param localDec local variable
     */
	public void add(LocalDec localDec) {
		if (!this.localDecs.contains(localDec)){
			this.localDecs.add(localDec);
			localDec.setMethDec(this);
		}
	}
	
	/**
	 * Adds a formal parameter to the method.
	 * 
	 * @param formalPar formal parameter
	 */
	public void add(FormalPar formalPar){
		if (!this.formalParameters.contains(formalPar)){
			this.formalParameters.add(formalPar);
		}
	}
	
	/**
	 * Adds an instruction.
	 * 
	 * @param instr instruction
	 */
	public void add(Instr instr){
		this.instructions.add(instr);
	}
	
	/**
	 * Gets the lis of local variables.
	 * 
	 * @return local variable
	 */
	public List<LocalDec> getLocalDecs(){
		return this.localDecs;
	}
    
    /** 
     * Constructor.
     * 
     * @param name name
     */    
    public MethDec(Name name) {
        super(name);       
        this.arguments = new ArrayList<FormalArgument>();
        this.localDecs = new ArrayList<LocalDec>();
        this.instructions = new ArrayList<Instr>();
        this.formalParameters = new ArrayList<FormalPar>();
    }
    
    /**
     * Adds a formal argument to the signature.
     *  
     * @param formalArgument formal argument
     */
    public void add(FormalArgument formalArgument){
    	if (!this.arguments.contains(formalArgument)){
    		arguments.add(formalArgument);
    		formalArgument.setMethDec(this);
    	}
    }
    
    /**
     * Adds a formal argument to the signature.
     *  
     * @param formalArgument formal argument
     * @param index index         
     * @since JavaMoon-2.0.2
     */
    public void add(FormalArgument formalArgument, int index){
    	if (!this.arguments.contains(formalArgument)){
    		arguments.add(index,formalArgument);
    		formalArgument.setMethDec(this);
    	}
    }
    
    /** 
     * Queries if the method is implemented.
     * 
     * @return true if the method is deferred, otherwise false
     */    
    public boolean isDeferred(){
    	return this.deferred;
    }
    
    /** 
     * Queries if the method is a constructor. We consider a 
     * method as constructor when it is used to instantiate 
     * new objects.
     * 
     * @return true if the method is constrcuctor otherwise false
     */    
    public abstract boolean isConstructor();
    	
    
    /** 
     * Remove the formal argument from the signature.
     * 
     * Shift the rest of the arguments.
     * 
     * 
     * @param index position of the argument
     */    
    public void removeFormalArg(int index){
        this.arguments.remove(index);
    }
    
    /**
     * Removes a formal parameter.
     * 
     * @param formalParameter formal parameter
     */
    public void remove(FormalPar formalParameter){
    	this.formalParameters.remove(formalParameter);
    }
    
    /**
     * Removes a formal argument.
     * 
     * @param formalArgument formal parameter
     * @since JavaMoon-2.0.2
     */
    public void remove(FormalArgument formalArgument){
    	this.arguments.remove(formalArgument);
    }
    
    /**
     * Removes a local declaration.
     * 
     * @param localDec local declaration 
     */
    public void remove(LocalDec localDec){
    	this.localDecs.remove(localDec);
    }
    
    
	/**
	 * Removes an instruction.
	 * 
	 * @param instr instruction
	 */
	public void remove(Instr instr){
		this.instructions.remove(instr);
	}
	
    /** 
     * Sets the deferred state of the current method
     * .
     * @param pDeferred deferred
     */    
    public void setDeferred(boolean pDeferred){
        this.deferred = pDeferred;
    }

    /**
     * Method from Pattern Design Visitor.
     * By RMS.
     * 
     * @param visitor visitor
     */
    public void accept(Visitor visitor){
    	// Hook method
    	visitor.visit(this);
    }      
    
    /**
     * Returns the class tha contains this method.
     * 
     * @return origin class
     * @see #setClassDef
     * @since JavaMoon-1.3.5
     */
    public ClassDef getClassDef(){
    	return classDef;
    }
    
    /**
     * Changes the class of the method.
     * 
     * @param classDef new class
     * @since JavaMoon-1.3.5
     * @see #getClassDef
     */
    public void setClassDef(ClassDef classDef){
    	this.classDef = classDef;
    }    
    
	/**
	 * Gets the unique name.
	 * 
	 * Name convention: classUniqueName~nameMethod#typeArg1#typeArg2
	 * 
	 * @return unique name
	 */
	@Override
	public Name getUniqueName() {		
		return this.getClassDef().getUniqueName().concat(
				SEPARATOR + this.getName().toString() + getListOfFormalArguments());
	}
	
	/**
	 * Gets the lisf of formal argument in a string format.
	 * 
	 * @return textual representation of arguments
	 */
	private String getListOfFormalArguments(){		
		StringBuilder sb = new StringBuilder();
		for (FormalArgument fa : this.getFormalArgument()){			
			sb.append("%");
			sb.append(fa.getType().getName());			
		}
		return sb.toString();
	}   
	
	/**
	 * Returns true if this is a generic method.
	 * 
	 * @return true when it has formal parameters
	 */
	public boolean isGeneric(){
		return (this.formalParameters.size()>0);
	}
	
	/**
	 * Looks up if exists a formal parameter with this name.
	 * 
	 * @param name name
	 * @return true if exists
	 */
	public boolean hasFormalPar(Name name){
		for (FormalPar fp : this.formalParameters){
			if (fp.getName().equals(name)){
				return true;
			}
		}
		return false;
	}	
	
	/**
	 * Gets a formal parameter by name.
	 * 
	 * @param name name
	 * @return formal parameter
	 */
	public FormalPar getFormalPar(Name name){
		for (FormalPar fp : this.formalParameters){
			if (name.equals(fp.getName())){
				return fp;
			}
		}
		return null;
	}
	
	/**
	 * Gets the index of a formal parameter by name.
	 * 
	 * @param name formal parameter name
	 * @return the index or -1 if it is not found
	 */
	public int getFormalParIndex(Name name){
		int i = 0;
		for (FormalPar fp : this.formalParameters){
			if (name.toString().equals(fp.getName().toString())){
				return i;
			}
			i++;
		}
		return -1;
	}
	
	/**
	 * Queries if the method returns something.
	 * 
	 * @return true if it has return type (is a function)
	 */
	public abstract boolean hasReturnType();
	
	/**
	 * Creates a copy of current instructions.
	 * 
	 * @return copy of current instructions
	 */
	public List<Instr> copy(){
		List<Instr> aux = new ArrayList<Instr>();
		for (Instr instr : this.getInstructions()){
			if (instr instanceof CompoundInstr){
				 instr = (Instr) ((CompoundInstr) instr).clone();				 
			}
			aux.add(instr);
		}
		return aux;
	}
        
	/**
	 * Util method to flat instructions.
	 * 
	 * @param compound compound instruction
	 * @param newBodyMethod instruction list
	 */
	private void visit(CompoundInstr compound, List<Instr> newBodyMethod){
		for (Instr instr : compound.getInstructions()){
			if (! (instr instanceof CompoundInstr )){				
				newBodyMethod.add(instr);
			}
			else {
				visit((CompoundInstr)instr, newBodyMethod);
			}
		}
	}
	
	/**
	 * Checks if the methods have the same identical signature.
	 * 
	 * @param otherMethod initial method
	 * @return true if it is subsignature, false in other case
	 */
	public abstract boolean hasSameSignature(MethDec otherMethod);
	
	/**
	 * Checks if the method is a subsignature.
	 * 
	 * @param otherMethod initial method
	 * @return true if the method is subsignature, false in other case
	 */
	public abstract boolean isSubsignatureOf(MethDec otherMethod);
	
	/**
	 * Checks if the method is more specific.
	 * 
	 * @param otherMethod initial method
	 * @return true if the method is more specific, false in other case
	 */
	public abstract boolean isMoreSpecific(MethDec otherMethod);
	
	/**
	 * Checks if exists other method in ancestors with same subsignature.
	 * 
	 * @return true if exists, false in other case
	 */
	public boolean isModifiedInInheritance(){
		Collection<ClassDef> ancestors = this.getClassDef().getAncestors();
		for (ClassDef ancestor : ancestors){
			if (ancestor == this.getClassDef())	continue; // skip current class
			for (MethDec methDec : ancestor.getMethDec()){
				 if (this.isSubsignatureOf(methDec)){
					 return true;
				 }
			}
		}
		return false;
	}
} // MethDec